// ExportToDoList.cpp: implementation of the CExportToDoListTo... classes.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ExportToDoList.h"
#include "todoctrl.h" // for styles only
#include "tdlschemadef.h"

#include "..\shared\timeedit.h" 

//////////////////////////////////////////////////////////////////////
// Base class
//////////////////////////////////////////////////////////////////////

// copied from todoctrl.h
const COLORREF DEFPRIORITYCOLORS[] = 
{
	RGB(240, 240, 240),	// 0
	RGB(216, 216, 216),	// 1
	RGB(192, 192, 192),	// 2
	RGB(168, 168, 168),	// 3
	RGB(144, 144, 144),	// 4
	RGB(120, 120, 120),	// 5
	RGB(96, 96, 96),	// 6
	RGB(72, 72, 72),	// 7
	RGB(48, 48, 48),	// 8
	RGB(24, 24, 24),	// 9
	0,					// 10
};

CExportToDoList::CExportToDoList(const EXPORTPARAMS& ep) : m_ep(ep)
{
}

CExportToDoList::~CExportToDoList()
{
}

int CExportToDoList::CalcPercentDone(const CXmlItem* pItem) const
{
	int nPercent = pItem->GetItemValueF(TDL_TASKDONEDATE) > 0 ? 100 : pItem->GetItemValueI(TDL_TASKPERCENTDONE); // default

	if (nPercent < 100 && HasPref(TDCS_AVERAGEPERCENTSUBCOMPLETION))
	{
		const CXmlItem* pXIChild = pItem->GetItem(TDL_TASK);

		if (pXIChild)
		{
			int nNumChildren = 0;
			nPercent = 0;
			BOOL bIncludeDone = HasPref(TDCS_INCLUDEDONEINAVERAGECALC);

			while (pXIChild)
			{
				if (bIncludeDone || pItem->GetItemValueF(TDL_TASKDONEDATE) == 0)
				{
					nPercent += CalcPercentDone(pXIChild);
					nNumChildren++;
				}

				pXIChild = pXIChild->GetSibling();
			}

			// average
			nPercent /= nNumChildren;
		}
	}

	return nPercent;
}

double CExportToDoList::CalcTimeEstimateInHours(const CXmlItem* pItem) const
{
	double dTime = 0;
	const CXmlItem* pXIChild = pItem->GetItem(TDL_TASK);

	if (!pXIChild)
	{
		dTime = pItem->GetItemValueF(TDL_TASKTIMEESTIMATE);
		int nUnits = GetTimeUnits(pItem, TDL_TASKTIMEESTUNITS);

		return CTimeEdit::GetTime(dTime, nUnits, TEU_HOURS);
	}

	// else aggregate child times
	while (pXIChild)
	{
		dTime += CalcTimeEstimateInHours(pXIChild);
		
		pXIChild = pXIChild->GetSibling();
	}

	return dTime;
}

int CExportToDoList::GetTimeUnits(const CXmlItem* pItem, LPCTSTR szType) const
{
	CString sUnits = pItem->GetItemValue(szType);
	
	if (!sUnits.IsEmpty())
		return (int)sUnits[0];

	// else
	return TEU_HOURS;
}

double CExportToDoList::CalcTimeSpentInHours(const CXmlItem* pItem) const
{
	double dTime = 0;
	const CXmlItem* pXIChild = pItem->GetItem(TDL_TASK);

	if (!pXIChild)
	{
		dTime = pItem->GetItemValueF(TDL_TASKTIMESPENT);
		int nUnits = GetTimeUnits(pItem, TDL_TASKTIMESPENTUNITS);

		return CTimeEdit::GetTime(dTime, nUnits, TEU_HOURS);
	}

	// else aggregate child times
	while (pXIChild)
	{
		dTime += CalcTimeSpentInHours(pXIChild);
		
		pXIChild = pXIChild->GetSibling();
	}

	return dTime;
}

BOOL CExportToDoList::WantTitleCommentsOnly(const CXmlItem* pItem) const
{
	return (m_ep.bParentTitleCommentsOnly && pItem->GetItem(TDL_TASK));
}

BOOL CExportToDoList::WantSpaceForNotes(const CXmlItem* pItem) const
{
	return (m_ep.bSpaceForNotes && !WantTitleCommentsOnly(pItem));
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

const LPCTSTR ENDL = "\n";
const LPCTSTR HTMLNOTES = "<pre>\n\n\n\n\n\n\n\n</pre>";

CExportToDoListToHtml::CExportToDoListToHtml(const EXPORTPARAMS& ep, 
											 const CDWordArray& aColors, COLORREF crDone, 
											 LPCTSTR szFontName, int nFontSize) : 
	CExportToDoList(ep), 
	m_crDone(crDone),
	m_sFontName(szFontName), 
	m_nFontSize(nFontSize)
{
	m_aPriorityColors.Copy(aColors);
}

CExportToDoListToHtml::~CExportToDoListToHtml()
{

}

CString& CExportToDoListToHtml::Export(const CXmlItem* pItem, int nDepth, int nPos, CString& sOutput) const
{
	// if depth == 0 then we're at the root so just add sub-tasks
	// else insert pItem first
	if (nDepth > 0)
	{
		// if there is a POS child item then this replaces nPos
		if (pItem->GetItem(TDL_TASKPOS))
			nPos = pItem->GetItemValueI(TDL_TASKPOS);

		BOOL bTitleCommentsOnly = WantTitleCommentsOnly(pItem);

		CString sItem, sColor("#000000"), sPriority, sAllocTo, sAllocBy, sTitle, sPercent;
		CString sStatus, sCategory, sTimeEst, sTimeSpent, sFileRef, sComments;
		CString sStartDate, sDueDate, sDoneDate, sID;

		// ID
		if (!bTitleCommentsOnly && pItem->GetItem(TDL_TASKID))
			sID.Format("(ID: %d) ", pItem->GetItemValueI(TDL_TASKID));

		// title
		if (nDepth == 1) // toplevel == bold
			sTitle.Format("<b>%s</b>", pItem->GetItemValue(TDL_TASKTITLE));
		else
			sTitle = pItem->GetItemValue(TDL_TASKTITLE);

		// priority, start/due/done dates
		int nPercent = CalcPercentDone(pItem);
		BOOL bDoneDate = (pItem->GetItemValueF(TDL_TASKDONEDATE) > 0);
		BOOL bDone = (nPercent == 100 || bDoneDate);
		COLORREF color = 0;

		if (bDone)
		{
			color = m_crDone;

			if (!bTitleCommentsOnly && bDoneDate)
				sDoneDate.Format(" (%s)", pItem->GetItemValue(TDL_TASKDONEDATESTRING));
		}
		else if (!bTitleCommentsOnly)
		{
			color = CalcItemColor(pItem);

			if (!bTitleCommentsOnly && pItem->GetItem(TDL_TASKPRIORITY))
				sPriority.Format("[%s] ", pItem->GetItemValue(TDL_TASKPRIORITY));

			if (pItem->GetItem(TDL_TASKSTARTDATESTRING))
				sStartDate.Format(" (start: %s)", pItem->GetItemValue(TDL_TASKSTARTDATESTRING));

			if (pItem->GetItem(TDL_TASKDUEDATESTRING))
				sDueDate.Format(" (due: %s)", pItem->GetItemValue(TDL_TASKDUEDATESTRING));

			if (pItem->GetItem(TDL_TASKPERCENTDONE))
				sPercent.Format(" (%d%%) ", nPercent);

			double dTimeEst = CalcTimeEstimateInHours(pItem);
			double dTimeSpent = CalcTimeSpentInHours(pItem);
			BOOL bRound = HasPref(TDCS_ROUNDTIMEFRACTIONS);

			if (dTimeEst)
				sTimeEst.Format(" (time est: %.*f hrs)", bRound ? 0 : 2, dTimeEst);

			if (dTimeSpent)
				sTimeSpent.Format(" (time spent: %.*f hrs)", bRound ? 0 : 2, dTimeEst);
		}

		// format color string
		sColor.Format("#%02X%02X%02X", GetRValue(color), GetGValue(color), GetBValue(color));

		// allocated to
		if (!bTitleCommentsOnly)
		{
			if (pItem->GetItem(TDL_TASKALLOCTO))
				sAllocTo.Format(" (allocated to: %s)", pItem->GetItemValue(TDL_TASKALLOCTO));

			// allocated by
			if (pItem->GetItem(TDL_TASKALLOCBY))
				sAllocBy.Format(" (allocated by: %s)", pItem->GetItemValue(TDL_TASKALLOCBY));

			// category
			if (pItem->GetItem(TDL_TASKCATEGORY))
				sCategory.Format(" (category: %s)", pItem->GetItemValue(TDL_TASKCATEGORY));

			// status
			if (pItem->GetItem(TDL_TASKSTATUS))
				sStatus.Format(" (status: %s)", pItem->GetItemValue(TDL_TASKSTATUS));

			// fileref
			if (pItem->GetItem(TDL_TASKFILEREFPATH))
			{
				CString sFilePath = pItem->GetItemValue(TDL_TASKFILEREFPATH);
				sFileRef.Format("<br>(file ref: <a href=\"%s\">%s</a>)", sFilePath, sFilePath);
			}
		}

		BOOL bGrayed = (IsItemDone(pItem, TRUE) || bDone);

		// comments
		if (pItem->GetItem(TDL_TASKCOMMENTS))
		{
			CString sItemComments(pItem->GetItemValue(TDL_TASKCOMMENTS));
			TXT2XML(sItemComments); // TODO

			sComments.Format(bGrayed ? "<br><font color='#c4c4c4'>[%s] </font>" : "<br><font color='#606060'>[%s] </font>", 
							sItemComments);

			// replace carriage returns with <br>
			sComments.Replace(ENDL, "<br>");
			sComments.Replace("\n", "<br>");

			// replace tab characters with multiple &nbsp;
			sComments.Replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
		}

		CString sFormat;
		
		if (bDone)
		{
			if (HasPref(TDCS_STRIKETHOUGHDONETASKS))
				sFormat = "%s%s%s<font color='%s'><s>%s%s%s%s%s%s%s%s</s></font>%s<s>%s</s>%s";
			else
				sFormat = "%s%s%s<font color='%s'>%s%s%s%s%s%s%s%s</font>%s%s%s";
		}
		else if (bGrayed)
			sFormat = "%s%s%s<font color='%s'>%s%s%s%s%s%s%s%s</font>%s%s%s";
		else
			sFormat = "%s%s%s<font color='%s'>%s</font>%s%s%s%s%s%s%s%s%s%s";

		sItem.Format(sFormat, sID, sPriority, sPercent, sColor, 
					sTitle, sAllocTo, sAllocBy, sCategory, sStatus, 
					sStartDate, sDueDate, sTimeEst, sDoneDate, sComments, sFileRef);

		// notes section
		if (!bDone && WantSpaceForNotes(pItem))
			sItem += HTMLNOTES;

		if (nDepth == 1) // toplevel
			sOutput += "<br>";

		sOutput += "<li>";
		sOutput += sItem;
	}
	else
	{
		if (!m_sFontName.IsEmpty())
			sOutput.Format("<font face='%s' size='%d' >", m_sFontName, m_nFontSize);

		// project name
		if (!m_ep.sProjectName.IsEmpty())
		{
			CString sProjTitle;
			sProjTitle.Format("<h2>%s</h2>", m_ep.sProjectName);
			sOutput += sProjTitle;
		}
	}

	// begin new ordered list for sub-tasks
	const CXmlItem* pTask = pItem->GetItem(TDL_TASK);

	if (pTask) // at least one sub-task
	{
		sOutput += ENDL;
		sOutput += "<ol>";
		sOutput += ENDL;

		int nChildPos = 1;

		while (pTask)
		{
			CString sTask;
			sOutput += Export(pTask, nDepth + 1, nChildPos++, sTask);

			pTask = pTask->GetSibling();
		}

		// end ordered list
		sOutput += "</ol>";
		sOutput += ENDL;
	}

	// end of item
	if (nDepth > 0)
	{
		sOutput += "</li>";
		sOutput += ENDL;
	}
	else if (!m_sFontName.IsEmpty()) // && nDepth == 0
		sOutput += "</font>";

	return sOutput;
}

COLORREF CExportToDoListToHtml::GetPriorityColor(int nPriority) const
{
	if (nPriority < 0 || nPriority > 10)
		return GetSysColor(COLOR_WINDOW);

	if (HasPref(TDCS_COLORPRIORITY))
		return (COLORREF)m_aPriorityColors[nPriority];

	// else default grayscale
	return DEFPRIORITYCOLORS[nPriority];
}

COLORREF CExportToDoListToHtml::CalcItemColor(const CXmlItem* pItem) const
{
	COLORREF color = 0; // black
	BOOL bParentDone = FALSE, bItemDone = IsItemDone(pItem, FALSE);

	if (!bItemDone)
		bParentDone = IsItemDone(pItem, TRUE);

	if (bParentDone || bItemDone)
		color = m_crDone; // parent and/or item is done
				
	else if (IsItemDue(pItem))
	{
		if (HasPref(TDCS_COLORTEXTBYPRIORITY))
			color = GetPriorityColor(10);
		else
			color = 255;
	}
	else if (HasPref(TDCS_COLORTEXTBYPRIORITY))
		color = GetPriorityColor(pItem->GetItemValueI(TDL_TASKPRIORITY)); 
				
	else if (pItem->GetItem(TDL_TASKCOLOR))
		color = pItem->GetItemValueI(TDL_TASKCOLOR);

	return color;
}

BOOL CExportToDoListToHtml::IsItemDone(const CXmlItem* pItem, BOOL bCheckParent) const
{
	if (pItem->GetItemValueF(TDL_TASKDONEDATE) > 0)
		return TRUE;

	else if (bCheckParent && pItem->GetParent())
		return IsItemDone(pItem->GetParent(), TRUE);

	else
		return FALSE;
}

BOOL CExportToDoListToHtml::IsItemDue(const CXmlItem* pItem) const
{
	if (!pItem->GetItem(TDL_TASKDUEDATE) || IsItemDone(pItem, TRUE))
		return FALSE;
			
	COleDateTime today = COleDateTime::GetCurrentTime();
	today = COleDateTime(today.GetYear(), today.GetMonth(), today.GetDay(), 23, 59, 59); // end of today

	return (pItem->GetItemValueF(TDL_TASKDUEDATE) <= today.m_dt); 
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

const LPCTSTR TEXTNOTES = "\n\n\n\n\n\n\n\n";

CExportToDoListToText::CExportToDoListToText(const EXPORTPARAMS& ep, int nIndentWidth)
	 : CExportToDoList(ep), INDENT(' ', nIndentWidth)
{

}

CExportToDoListToText::~CExportToDoListToText()
{

}

CString& CExportToDoListToText::Export(const CXmlItem* pItem, int nDepth, int nPos, CString& sOutput) const
{
	// if depth == 0 then we're at the root so just add sub-tasks
	// else insert pItem first
	if (nDepth > 0)
	{
		// first create string to hold TABs
		CString sTabs;

		for (int nTab = 0; nTab < nDepth; nTab++)
			sTabs += INDENT;

		// if there is a POS child item then this replaces nPos
		if (pItem->GetItem(TDL_TASKPOS))
			nPos = pItem->GetItemValueI(TDL_TASKPOS);

		BOOL bTitleCommentsOnly = WantTitleCommentsOnly(pItem);

		CString sID, sItem, sPriority, sStartDate, sDueDate, sDoneDate;
		CString sAllocTo, sAllocBy, sCategory, sStatus;
		CString sComments, sPercent, sTimeEst, sTimeSpent, sFileRef;

		// ID
		if (!bTitleCommentsOnly && pItem->GetItem(TDL_TASKID))
			sID.Format("(ID: %d) ", pItem->GetItemValueI(TDL_TASKID));

		// title
		CString sTitle(pItem->GetItemValue(TDL_TASKTITLE));

		// priority, start/due/done dates
		int nPercent = CalcPercentDone(pItem);
		BOOL bDoneDate = (pItem->GetItemValueF(TDL_TASKDONEDATE) > 0);
		BOOL bDone = (nPercent == 100 || bDoneDate);

		if (bDone)
			sDoneDate.Format(" (completed: %s)", pItem->GetItemValue(TDL_TASKDONEDATESTRING));

		else if (!bTitleCommentsOnly)
		{
			sPriority.Format("[%s] ", pItem->GetItemValue(TDL_TASKPRIORITY));

			if (pItem->GetItem(TDL_TASKSTARTDATESTRING))
				sStartDate.Format(" (start: %s)", pItem->GetItemValue(TDL_TASKSTARTDATESTRING));

			if (pItem->GetItem(TDL_TASKDUEDATESTRING))
				sDueDate.Format(" (due: %s)", pItem->GetItemValue(TDL_TASKDUEDATESTRING));


			double dTimeEst = CalcTimeEstimateInHours(pItem);
			double dTimeSpent = CalcTimeSpentInHours(pItem);
			BOOL bRound = HasPref(TDCS_ROUNDTIMEFRACTIONS);

			if (dTimeEst)
				sTimeEst.Format(" (time est: %.*f hrs)", bRound ? 0 : 2, dTimeEst);

			if (dTimeSpent)
				sTimeSpent.Format(" (time spent: %.*f hrs)", bRound ? 0 : 2, dTimeEst);
		}

		if (!bTitleCommentsOnly)
		{
			sPercent.Format(" (%d%%) ", nPercent);

			// allocated to
			if (pItem->GetItem(TDL_TASKALLOCTO))
				sAllocTo.Format(" (allocated to: %s)", pItem->GetItemValue(TDL_TASKALLOCTO));

			// allocated by
			if (pItem->GetItem(TDL_TASKALLOCBY))
				sAllocBy.Format(" (allocated by: %s)", pItem->GetItemValue(TDL_TASKALLOCBY));

			// category
			if (pItem->GetItem(TDL_TASKCATEGORY))
				sCategory.Format(" (category: %s)", pItem->GetItemValue(TDL_TASKCATEGORY));

			// status
			if (pItem->GetItem(TDL_TASKSTATUS))
				sStatus.Format(" (status: %s)", pItem->GetItemValue(TDL_TASKSTATUS));

			// fileref
			if (pItem->GetItem(TDL_TASKFILEREFPATH))
				sFileRef.Format("%s%s(file ref: %s)", ENDL, sTabs, pItem->GetItemValue(TDL_TASKFILEREFPATH));
		}
		
		// comments
		if (pItem->GetItem(TDL_TASKCOMMENTS))
			sComments.Format("%s%s[%s]", ENDL, sTabs, pItem->GetItemValue(TDL_TASKCOMMENTS));

		sItem.Format("%d. %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", 
					nPos, sID, sPriority, sPercent, sTitle, 
					sAllocTo, sAllocBy, sCategory, sStatus, 
					sDoneDate, sStartDate, sDueDate, sTimeEst, sTimeSpent, sFileRef, sComments, ENDL);

		// notes section
		if (!bDone && WantSpaceForNotes(pItem))
			sItem += TEXTNOTES;

		// indent to match depth
		sOutput += sTabs;
		sOutput += sItem;
	}
	else if (!m_ep.sProjectName.IsEmpty())
	{
		CString sUnderline('-', m_ep.sProjectName.GetLength());
		sOutput.Format("%s%s%s%s", m_ep.sProjectName, ENDL, sUnderline, ENDL);
	}

	// begin new ordered list for sub-tasks
	const CXmlItem* pTask = pItem->GetItem(TDL_TASK);

	if (pTask) // at least one sub-task
	{
		int nChildPos = 1;

		while (pTask)
		{
			CString sTask;
			sOutput += ENDL;
			sOutput += Export(pTask, nDepth + 1, nChildPos++, sTask);

			pTask = pTask->GetSibling();
		}
	}

	// extra line between top level items
	if (nDepth == 1)
		sOutput += ENDL;

	return sOutput;
}

